package com.huaguoguo.cyg.controller;


import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.huaguoguo.cyg.annotation.ResultModelBuilder;
import com.huaguoguo.cyg.entity.seckill.to.RedPacket;
import com.huaguoguo.cyg.exceptions.SecKillException;
import com.huaguoguo.cyg.seckill.queue.RedPacketMessage;
import com.huaguoguo.cyg.seckill.queue.RedPacketQueue;
import com.huaguoguo.cyg.service.seckill.RedPacketService;
import com.huaguoguo.cyg.utils.RedisUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 高并发抢红包案例
 * https://blog.52itstyle.vip
 */
@Api(tags = "抢红包")
@RestController
@RequestMapping("/redPacket")
@Slf4j
public class RedPacketController {

    // 获取jvm可用的处理器数
    private static int corePoolSize = Runtime.getRuntime().availableProcessors();
    //创建线程池  调整队列数 拒绝服务
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize + 1, 10l, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000));

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RedPacketService redPacketService;

    /**
     * 发布一个红包
     * @param redPacketNum 红包份数
     * @param totalMoney 红包总金额 单位：分
     * @return
     */
    @PostMapping("publish")
    @ResultModelBuilder
    public Object publishRedPack(Integer redPacketNum, Integer totalMoney){
        long redPacketId = Instant.now().toEpochMilli();
        log.info("红包id：{}",redPacketId);
        // 拆分红包
        List<Integer> redPackList = redPacketService.splitRedPack(redPacketNum, totalMoney);
        // 红包俺编号存入redis
        for (int i = 0; i < redPackList.size(); i++) {
            String key = new StringBuilder().append(redPacketId).append("-").append(i).toString();
            redisUtil.cacheValue(key,redPackList.get(i));
        }
        // 放入红包数量
        redisUtil.cacheValue(redPacketId+"-num",redPacketNum);
        // 返回红包信息
        return redPackList.stream()
                .map(amount -> RedPacket.builder().redPacketId(redPacketId).redPackAmount(amount).build())
                .collect(Collectors.toList());
    }

    /**
     * 用户抢红包请求入口
     * @param redPacketId 红包id
     * @return
     */
    @GetMapping("rob")
    @ResultModelBuilder
    public Object robRedPack(Long redPacketId){
        // 分配一个userId，用UUID
        String userId = UUID.randomUUID().toString(true);
        String message = null;
        // 抢红包 判断剩余数量
        Integer restNum = (Integer) redisUtil.getValue(redPacketId + "-num");
        /**
         * 虽然能抢到 但是不一定能拆到
         * 类似于微信的 点击红包显示抢的按钮
         */
        if (restNum > 0) {
            try {
                long redPackNo = redPacketService.startTwoSeckil(redPacketId, userId);
                // 取红包金额
                String key = new StringBuilder().append(redPacketId).append("-").append(redPackNo).toString();
                Integer money = Integer.parseInt(redisUtil.getValue(key).toString());
                Double amount = NumberUtil.div(money.doubleValue(), 100.0);
                message = StrUtil.format("用户{}抢红包【{}】成功，金额：{}元", userId, redPacketId,amount);
                log.info(message);
            } catch (SecKillException e) {
                e.printStackTrace();
                message = StrUtil.format("用户{}手慢了，红包【{}】派完了", userId,redPacketId);
                log.info(message);
            }
        } else {
            /**
             * 直接显示手慢了，红包派完了
             */
            message = StrUtil.format("用户{}手慢了，红包【{}】派完了", userId,redPacketId);
            log.info(message);
        }
        return message;
    }

    /**
     * 用多线程模拟多个用户抢红包
     * 抢红包 拆红包 抢到不一定能拆到
     * @param redPacketNum  红包数量
     * @param redPacketMoney 红包金额
     * @return
     */
    @ApiOperation(value = "抢红包二", nickname = "爪哇笔记")
    @PostMapping("/startTwo")
    @ResultModelBuilder
    public Object startTwo(Integer redPacketNum,Integer redPacketMoney) {
        long redPacketId = Instant.now().toEpochMilli();
        log.info("红包id：{}",redPacketId);
        int skillNum = 100;
        //N个人抢红包
        final CountDownLatch latch = new CountDownLatch(skillNum);
        // 拆分红包
        List<Integer> redPackList = redPacketService.splitRedPack(redPacketNum, redPacketMoney);
        // 红包俺编号存入redis
        for (int i = 0; i < redPackList.size(); i++) {
            String key = new StringBuilder().append(redPacketId).append("-").append(i).toString();
            redisUtil.cacheValue(key,redPackList.get(i));
        }
        // 放入红包数量
        redisUtil.cacheValue(redPacketId+"-num",redPacketNum);
        /**
         * 模拟100个用户抢10个红包
         */
        for (int i = 1; i <= skillNum; i++) {
            String userId = String.valueOf(i);
            Runnable task = () -> {
                // 抢红包 判断剩余数量
                Integer restNum = (Integer) redisUtil.getValue(redPacketId + "-num");
                /**
                 * 虽然能抢到 但是不一定能拆到
                 * 类似于微信的 点击红包显示抢的按钮
                 */
                if (restNum > 0) {
                    try {
                        long redPackNo = redPacketService.startTwoSeckil(redPacketId, userId);
                        // 取红包金额
                        String key = new StringBuilder().append(redPacketId).append("-").append(redPackNo).toString();
                        Integer money = Integer.parseInt(redisUtil.getValue(key).toString());
                        Double amount = NumberUtil.div(money.doubleValue(), 100.0);
                        log.info("用户{}抢红包成功，金额：{}元", userId, amount);
                    } catch (SecKillException e) {
                        e.printStackTrace();
                        log.info("用户{}手慢了，红包派完了", userId);
                    }
                } else {
                    /**
                     * 直接显示手慢了，红包派完了
                     */
                    log.info("用户{}手慢了，红包派完了", userId);
                }
                latch.countDown();
            };
            executor.execute(task);
        }
        try {
            latch.await();
            Integer restNum = Integer.parseInt(redisUtil.getValue(redPacketId + "-num").toString());
            log.info("剩余红包数量：{}", restNum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "红包抢完了";
    }

    /**
     * 有人没抢 红包发多了
     * 红包进入延迟队列
     * 实现过期失效
     * @param redPacketNum  红包数量
     * @param redPacketMoney 红包金额
     * @return
     */
    @ApiOperation(value = "抢红包三", nickname = "爪哇笔记")
    @PostMapping("/startThree")
    public Object startThree(Integer redPacketNum,Integer redPacketMoney) {
        long redPacketId = Instant.now().toEpochMilli();
        log.info("红包id：{}",redPacketId);
        int skillNum = 9;
        //N个人抢红包
        final CountDownLatch latch = new CountDownLatch(skillNum);
        // 拆分红包
        List<Integer> redPackList = redPacketService.splitRedPack(redPacketNum, redPacketMoney);
        // 红包俺编号存入redis
        for (int i = 0; i < redPackList.size(); i++) {
            String key = new StringBuilder().append(redPacketId).append("-").append(i).toString();
            redisUtil.cacheValue(key,redPackList.get(i));
        }
        // 放入红包数量
        redisUtil.cacheValue(redPacketId+"-num",redPacketNum);
        /**
         * 加入延迟队列 24s秒过期
         */
        RedPacketMessage message = new RedPacketMessage(redPacketId, 5);
        RedPacketQueue.getQueue().produce(message);
        /**
         * 模拟 9个用户抢10个红包
         */
        for (int i = 1; i <= skillNum; i++) {
            String userId = String.valueOf(i);
            Runnable task = () -> {
                // 抢红包 判断剩余数量
                Integer restNum = (Integer) redisUtil.getValue(redPacketId + "-num");
                /**
                 * 虽然能抢到 但是不一定能拆到
                 * 类似于微信的 点击红包显示抢的按钮
                 */
                if (restNum > 0) {
                    try {
                        long redPackNo = redPacketService.startTwoSeckil(redPacketId, userId);
                        // 取红包金额
                        String key = new StringBuilder().append(redPacketId).append("-").append(redPackNo).toString();
                        Integer money = (Integer) redisUtil.getValue(key);
                        Double amount = NumberUtil.div(money.doubleValue(), 100.0);
                        log.info("用户{}抢红包成功，金额：{}元", userId, amount);
                    } catch (SecKillException e) {
                        e.printStackTrace();
                        log.info("用户{}手慢了，红包派完了", userId);
                    }
                } else {
                    /**
                     * 直接显示手慢了，红包派完了
                     */
                    log.info("用户{}手慢了，红包派完了", userId);
                }
                latch.countDown();
            };
            executor.execute(task);
        }
        try {
            latch.await();
            Integer restNum = Integer.parseInt(redisUtil.getValue(redPacketId + "-num").toString());
            log.info("剩余红包数量：{}", restNum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "实现过期";
    }
}